//
// Created by Dell on 2021/5/31.
//
#include <string>
#include "scene.h"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/ext.hpp"
#include "glm/detail/_noise.hpp"
#include "ggl.h"

/**
 * 通过着色器，在gpu中计算yuv转rgb
 *
 */
class Section802 : public Scene {
private:
    //初始化三个矩阵
    glm::mat4 projectMatrix, viewMatrix, modelMatrix, transformMatrix;
    GLuint shaderProgram;
    GLuint vbo, vao, ebo;
    GLuint vertexShader, fragmentShader;
    bool textureUpdate;
    int textureW, textureH;
    unsigned char *y, *u, *v;
    int yPixelStride, uPixelStride, vPixelStride;
    GLuint textureY, textureU, textureV;
public:

    explicit Section802(AAssetManager *aAssetManager) : Scene(aAssetManager) {
        y = nullptr;
        u = nullptr;
        v = nullptr;
    }

    ~Section802() {
        delete y;
        y = nullptr;
        delete u;
        u = nullptr;
        delete v;
        v = nullptr;
    }

    void init() override {
        //设置清屏颜色
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        int fileSize = 0;
        //顶点着色器
        unsigned char *a = LoadFileContent(aAssetManager, "802.vsh", fileSize);
        vertexShader = CompileShader(GL_VERTEX_SHADER, (char *) a);
        //片段着色器
        unsigned char *b = LoadFileContent(aAssetManager, "802.fsh", fileSize);
        fragmentShader = CompileShader(GL_FRAGMENT_SHADER, (char *) b);
        //创建渲染程序
        shaderProgram = CreateProgram(vertexShader, fragmentShader);
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);


        //strip顶点数据设置 齐次坐标|纹理坐标 为了铺满屏幕，直接选择填充-1到1的范围，顶点顺序按顺时针或者逆时
        //针，这样方便判断在空间中三角形是正面面向摄像机，还是背面面向摄像机，若开启背面裁剪，则可以忽略背面的
        //三角形渲染，提高性能
        GLfloat vertices[] = {
                -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f,//左下
                1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f,//右下
                -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,//左上
                1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f,//右上
        };
        //创建顶点数组对象
        glGenVertexArrays(1, &vao);
        //绑定顶点数组对象，后续写入的顶点缓冲对象都会加入奥vao中，绘制时只需要绑定vao即可
        glBindVertexArray(vao);
        //创建顶点缓冲对象,将顶点数据写入
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        //启用location对应的顶点属性,设置顶点属性指针，用于向gpu解释之前输入的顶点缓冲数据怎么解析到每个顶点上
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid *) nullptr);
        glEnableVertexAttribArray(4);
        glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat),
                              (void *) (sizeof(GLfloat) * 4));
        //解绑vbo
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        //解绑vao
        glBindVertexArray(0);

        //显存申请一个索引缓冲对象，用于指定绘制的点的索引，多个三角形可以通过这个来减少重复点的位置声明
        //上面的顶点缓冲对象存储了模型的四个顶点信息，这里将四边形划分为两个三角形，根据下标，指示gl将每三个
        // 点组合成一个三角形
        unsigned short indexes[] = {0, 1, 2, 2, 1, 3};
        glGenBuffers(1, &ebo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);//绑定索引缓冲对象设置绘制点下标数据
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }

    void setViewPortSize(float width, float height) override {
        //场景投影 3d场景投影到2d视窗 如果只是屏幕展示，不需要这个
//        projectMatrix = glm::perspective(45.0f, width / height, 0.0f, 1000.0f);
        //仅将场景投影应用于修复视窗图象拉伸问题，gl默认会将坐标[-1,1]的范围作为视窗图象，填满视窗，由于视窗
        //并不是正方形会造成拉伸，所以需要根据视窗缩放比例来调整场景矩阵的缩放，这里以宽度填满视窗，高度缩放
        //到宽度的大小
        glm::mat4 project;
        projectMatrix = glm::scale(project, glm::vec3(1.0f, width / height, 1.0f));
        //设置相机得到视图变换矩阵
        viewMatrix = glm::lookAt(
                glm::vec3(0.0f, 0.0f, 1.0f),//相机位置
                glm::vec3(0.0f, 0.0f, 0.0f),//观察位置
                glm::vec3(0.0f, 1.0f, 0.0f)//相机正上方朝向
        );
    }

    void draw() override {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        if (y == nullptr || u == nullptr || v == nullptr) {
            return;
        }
        glUseProgram(shaderProgram);

        //更新纹理
        if (textureUpdate) {
            textureUpdate = false;
            //删除现有的纹理
            glDeleteTextures(1, &textureY);
            glDeleteTextures(1, &textureU);
            glDeleteTextures(1, &textureV);
            textureY = CreateTexture2D(y, textureW, textureH, GL_LUMINANCE);
            textureU = CreateTexture2D(u, textureW / 2, textureH / 2, GL_LUMINANCE);
            textureV = CreateTexture2D(v, textureW / 2, textureH / 2, GL_LUMINANCE);

            //纹理的设置顺序应该是，先启用index位置的纹理，然后绑定要操作的纹理，然后将纹理index设置到片段着
            //色器上
            GLint textureIndex = 0;
            glActiveTexture(GL_TEXTURE0 + textureIndex);
            glBindTexture(GL_TEXTURE_2D, textureY);
            glUniform1i(10, textureIndex);

            textureIndex++;
            glActiveTexture(GL_TEXTURE0 + textureIndex);
            glBindTexture(GL_TEXTURE_2D, textureU);
            glUniform1i(11, textureIndex);

            textureIndex++;
            glActiveTexture(GL_TEXTURE0 + textureIndex);
            glBindTexture(GL_TEXTURE_2D, textureV);
            glUniform1i(12, textureIndex);
        }

        //更新顶点数据变换矩阵
        glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(projectMatrix));
        glUniformMatrix4fv(1, 1, GL_FALSE, glm::value_ptr(viewMatrix));
        glUniformMatrix4fv(2, 1, GL_FALSE, glm::value_ptr(modelMatrix));
        glUniformMatrix4fv(5, 1, GL_FALSE, glm::value_ptr(transformMatrix));

        //绑定顶点数据和索引缓冲，进行element绘制
        glBindVertexArray(vao);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
        //绘制ELEMENT
        glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_SHORT, nullptr);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    }

    void setPictureData(int width,
                        int height,
                        unsigned char *yData,
                        int y_pixel_stride,
                        unsigned char *uData,
                        int u_pixel_stride,
                        unsigned char *vData,
                        int v_pixel_stride
    ) {
        textureW = width;
        textureH = height;
        delete y;
        delete u;
        delete v;
        y = yData;
        u = uData;
        v = vData;

        LOGI("y %zu", malloc_usable_size(y));
        LOGI("u %zu", malloc_usable_size(u));
        LOGI("v %zu", malloc_usable_size(v));
        yPixelStride = y_pixel_stride;
        uPixelStride = u_pixel_stride;
        vPixelStride = v_pixel_stride;
        //模型矩阵变化，图片宽高比例不同，这里保持宽度不变，高度根据图片高度自适应
        glm::mat4 model;
        modelMatrix = glm::scale(model, glm::vec3(1.0f, (float) height / (float) width, 1.0f));
        //图象坐标不同导致上下翻转，模型中心位于原点，模型变换将y缩放-1倍实现上下翻转
        modelMatrix = glm::scale(modelMatrix, glm::vec3(1.0f, -1.0f, 1.0f));
        textureUpdate = true;
    }

    void setRotate(float angle) override {
        transformMatrix = glm::rotate(glm::radians(angle), glm::vec3(0.0f, 0.0f, 1.0f));
    }
};
